aboutsummaryrefslogtreecommitdiffstats
path: root/src/routes/(app)/[username]/messages
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2024-02-09 22:29:14 -0800
committerAdam Soltys <[email protected]>2024-02-09 22:29:14 -0800
commite58799c21b44f5d08f3de622d3df526b337b3d0a (patch)
treec016992b51da1ded3526830285e68ffaf2fa9d9e /src/routes/(app)/[username]/messages
parente51acb47d5b82f75126277239baaab52dbce3b72 (diff)
downloadcoinos-ui-e58799c21b44f5d08f3de622d3df526b337b3d0a.tar.gz
coinos-ui-e58799c21b44f5d08f3de622d3df526b337b3d0a.zip
separate messages page
Diffstat (limited to 'src/routes/(app)/[username]/messages')
-rw-r--r--src/routes/(app)/[username]/messages/+page.server.js38
-rw-r--r--src/routes/(app)/[username]/messages/+page.svelte226
-rw-r--r--src/routes/(app)/[username]/messages/[recipient]/+page.server.js25
-rw-r--r--src/routes/(app)/[username]/messages/[recipient]/+page.svelte191
4 files changed, 307 insertions, 173 deletions
diff --git a/src/routes/(app)/[username]/messages/+page.server.js b/src/routes/(app)/[username]/messages/+page.server.js
index f0148be7..eeb883ee 100644
--- a/src/routes/(app)/[username]/messages/+page.server.js
+++ b/src/routes/(app)/[username]/messages/+page.server.js
@@ -1,25 +1,33 @@
-import { get } from "$lib/utils";
+import { auth, get } from "$lib/utils";
-export async function load({ params, parent, url }) {
+export async function load({ cookies, params, parent, url }) {
let { user, subject } = await parent();
+ let { pubkey } = subject;
let { since = 0 } = params;
- let messages = [];
+ let messages = [],
+ notes = [];
- try {
- messages = await get(`/${user.pubkey}/${since}/messages`);
+ if (pubkey) {
+ if (user) {
+ try {
+ messages = await get(`/${user.pubkey}/${since}/messages`);
+ messages = messages.sort((a, b) => b.created_at - a.created_at);
+ } catch (e) {
+ console.log(`failed to fetch nostr messages`, e);
+ }
+ }
- messages = messages
- .filter(
- (m) => m.recipient?.id === subject.id || m.author?.id === subject.id
- )
- .sort((a, b) => a.created_at - b.created_at);
+ try {
+ notes = await get(`/${pubkey}/notes`);
+ } catch (e) {
+ console.log(`failed to fetch nostr notes for ${pubkey}`, e);
+ }
- if (user.id === subject.id)
- messages = messages.filter((m) => m.recipient.id === m.author.id);
- } catch (e) {
- console.log(`failed to fetch nostr messages`, e);
+ notes.map((e) => {
+ e.seen = e.created_at;
+ });
}
- return { messages, user };
+ return { messages, notes }
}
diff --git a/src/routes/(app)/[username]/messages/+page.svelte b/src/routes/(app)/[username]/messages/+page.svelte
index 48855a26..4738ea2c 100644
--- a/src/routes/(app)/[username]/messages/+page.svelte
+++ b/src/routes/(app)/[username]/messages/+page.svelte
@@ -1,182 +1,92 @@
<script>
- import { cubicInOut } from "svelte/easing";
- import { browser } from "$app/environment";
import { t } from "$lib/translations";
- import { format } from "date-fns";
- import { scale, fade, fly } from "svelte/transition";
- import { enhance } from "$app/forms";
import Avatar from "$comp/Avatar.svelte";
- import Icon from "$comp/Icon.svelte";
- import { back, fail, focus } from "$lib/utils";
- import { sign, send, encrypt, decrypt } from "$lib/nostr";
import { event as e, password } from "$lib/store";
- import { tick, onMount } from "svelte";
- import { getEventHash } from "nostr-tools";
+ import { browser } from "$app/environment";
+ import { decrypt } from "$lib/nostr";
export let data;
- function fadeScale(
- node,
- { delay = 0, duration = 200, easing = (x) => x, baseScale = 0 }
- ) {
- const o = +getComputedStyle(node).opacity;
- const m = getComputedStyle(node).transform.match(/scale\(([0-9.]+)\)/);
- const s = m ? m[1] : 1;
- const is = 1 - baseScale;
-
- return {
- delay,
- duration,
- css: (t) => {
- const eased = easing(t);
- return `opacity: ${eased * o}; transform: scale(${
- eased * s * is + baseScale
- })`;
- },
- };
- }
+ let { messages, notes, invoices, sent, received, subject, user, rates } =
+ data;
+ $: refresh(data);
+ let refresh = (d) =>
+ ({ messages, notes, invoices, sent, received, subject, user, rates } = d);
- let { messages, subject, user } = data;
- let input, pane;
+ let keys = new Set();
+ let latest = [];
+ let ready;
- $: initialize($password);
- let initialize = async (p) => {
- await Promise.all(
- messages.map(
- async (event) => (event.message = await decrypt({ event, user }))
- )
- );
+ e.subscribe(async (event) => {
+ if (!(browser && event && ready)) return;
+ if (
+ event.recipient?.id === user.id &&
+ !~latest.findIndex((m) => m.id === event.id)
+ ) {
+ event.content = await decrypt({ event, user });
- messages = messages;
- tick().then(() => pane && (pane.scrollTop = pane.scrollHeight));
- };
+ let i = latest.findIndex((m) => m.pubkey === event.pubkey);
+ let popped;
+ if (~i) popped = latest.splice(i, 1);
+ else popped = latest.pop();
- e.subscribe(async (event) => {
- let found = ~messages.findIndex((m) => m.id === event?.id);
- if (event?.recipient.id === user.id && !found) {
- event.message = await decrypt({ event, user });
- messages.push(event);
- messages = messages;
- tick().then(() => pane && (pane.scrollTop = pane.scrollHeight));
+ latest.unshift(event);
+ latest = latest;
}
});
- let sent, submitting, message;
- let submit = async () => {
- submitting = true;
-
- let event = {
- pubkey: user.pubkey,
- created_at: Math.floor(Date.now() / 1000),
- kind: 4,
- tags: [["p", subject.pubkey]],
- };
-
- event.message = message;
-
- message = "";
-
- event.author = user;
- event.recipient = subject;
+ $: initialize($password);
+ let initialize = async (p) => {
+ let i = 0;
+ ready = false;
+ if (!(browser && user)) return;
+ while (i < messages.length) {
+ let event = messages[i];
+ i++;
- messages.push(event);
- messages = messages;
- tick().then(() => pane && (pane.scrollTop = pane.scrollHeight));
+ if (!(event.author && event.recipient)) continue;
- try {
- event.content = await encrypt({
- message: event.message,
- recipient: subject.pubkey,
- user,
- });
- event.id = getEventHash(event);
- await sign({ event, user });
- await send(event);
+ let k =
+ event.author.id === user.id
+ ? event.recipient?.pubkey
+ : event.author.pubkey;
- sent = true;
- } catch (e) {
- console.log(e);
- fail("Failed to send message");
+ if (!keys.has(k)) {
+ keys.add(k);
+ event.content = await decrypt({ event, user });
+ if (event.content) latest.push(event);
+ }
}
- message = "";
- submitting = false;
- };
-
- let keydown = (e) => {
- if (e.key === "Enter") {
- e.preventDefault();
- submit();
- }
+ latest = latest;
+ ready = true;
};
</script>
-<div class="container max-w-xl mx-auto px-4 space-y-5">
- <div
- class="h-[50vh] max-h-[50vh] overflow-y-scroll scrollbar-thin scrollbar-thumb-[#F2F6FC] scrollbar-track-white pr-8"
- bind:this={pane}
- >
- {#each messages as { id, author, message, created_at, pubkey }}
- {@const ours = pubkey === user.pubkey}
- {@const theirs = !ours}
-
- {#if message}
- <div
- class="flex gap-2"
- class:flex-row-reverse={theirs}
- class:justify-end={theirs}
- in:fadeScale={{
- easing: cubicInOut,
- baseScale: 0.5,
- }}
- >
- <div
- class="rounded-2xl px-4 py-2 max-w-[300px] mb-1 text-lg"
- class:ours
- class:theirs
- :key={id}
- >
- {message}
+<div class="container w-full mx-auto text-lg px-4 max-w-xl space-y-5">
+ <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold mb-10">
+ {$t("user.messages")}
+ </h1>
+ {#if latest.length && user?.id === subject.id}
+ <div class="relative">
+ {#each latest as { content, pubkey, author, recipient }}
+ {@const user = author.id === user.id ? recipient : author}
+ <a href={`/${subject.username}/messages/${user.username}`}>
+ <div class="flex hover:bg-gray-100 p-4 rounded-2xl">
+ <div class="my-auto">
+ <Avatar {user} size={"20"} disabled={true} />
+ </div>
+ <div class="my-auto truncate">
+ <div class="my-auto ml-1 text-lg font-bold">{user.username}</div>
+ <div class="my-auto ml-1 text-secondary text-lg truncate">
+ {content}
+ </div>
+ </div>
</div>
- <div class="mt-auto">
- <Avatar user={author} size={"12"} />
- </div>
- </div>
- <div class="text-sm text-gray-400 mb-6" class:text-right={ours}>
- {format(new Date(created_at * 1000), "MMM d")},
- {format(new Date(created_at * 1000), "h:mm aa")}
- </div>
- {/if}
- {/each}
- </div>
-
- <form
- method="POST"
- class="w-full border rounded-xl outline-none gap-4 flex p-0 pr-2"
- on:submit|preventDefault={submit}
- >
- <input type="hidden" name="requester_id" value={user.id} />
- <input type="hidden" name="recipient" value={subject.username} />
-
- <div
- use:focus
- contenteditable
- class="grow break-all py-4 outline-none mt-0 pl-4"
- bind:innerHTML={message}
- on:keydown={keydown}
- />
- <button type="submit" class="my-auto shrink-0">
- <Icon icon="send" style="w-8" />
- </button>
- </form>
+ </a>
+ {/each}
+ </div>
+ {:else}
+ <div class="text-center">{$t("user.noMessages")}</div>
+ {/if}
</div>
-
-<style>
- .ours {
- @apply bg-gradient-to-r from-[#F2F6FC] to-[#E1E3FF] text-black rounded-br-none ml-auto;
- }
-
- .theirs {
- @apply bg-gray-100 text-black rounded-bl-none;
- }
-</style>
diff --git a/src/routes/(app)/[username]/messages/[recipient]/+page.server.js b/src/routes/(app)/[username]/messages/[recipient]/+page.server.js
new file mode 100644
index 00000000..f0148be7
--- /dev/null
+++ b/src/routes/(app)/[username]/messages/[recipient]/+page.server.js
@@ -0,0 +1,25 @@
+import { get } from "$lib/utils";
+
+export async function load({ params, parent, url }) {
+ let { user, subject } = await parent();
+ let { since = 0 } = params;
+
+ let messages = [];
+
+ try {
+ messages = await get(`/${user.pubkey}/${since}/messages`);
+
+ messages = messages
+ .filter(
+ (m) => m.recipient?.id === subject.id || m.author?.id === subject.id
+ )
+ .sort((a, b) => a.created_at - b.created_at);
+
+ if (user.id === subject.id)
+ messages = messages.filter((m) => m.recipient.id === m.author.id);
+ } catch (e) {
+ console.log(`failed to fetch nostr messages`, e);
+ }
+
+ return { messages, user };
+}
diff --git a/src/routes/(app)/[username]/messages/[recipient]/+page.svelte b/src/routes/(app)/[username]/messages/[recipient]/+page.svelte
new file mode 100644
index 00000000..dfff6ed2
--- /dev/null
+++ b/src/routes/(app)/[username]/messages/[recipient]/+page.svelte
@@ -0,0 +1,191 @@
+<script>
+ import { cubicInOut } from "svelte/easing";
+ import { browser } from "$app/environment";
+ import { t } from "$lib/translations";
+ import { format } from "date-fns";
+ import { scale, fade, fly } from "svelte/transition";
+ import { enhance } from "$app/forms";
+ import Avatar from "$comp/Avatar.svelte";
+ import Icon from "$comp/Icon.svelte";
+ import { back, fail, focus } from "$lib/utils";
+ import { sign, send, encrypt, decrypt } from "$lib/nostr";
+ import { event as e, password } from "$lib/store";
+ import { tick, onMount } from "svelte";
+ import { getEventHash } from "nostr-tools";
+
+ export let data;
+
+ function fadeScale(
+ node,
+ { delay = 0, duration = 200, easing = (x) => x, baseScale = 0 }
+ ) {
+ const o = +getComputedStyle(node).opacity;
+ const m = getComputedStyle(node).transform.match(/scale\(([0-9.]+)\)/);
+ const s = m ? m[1] : 1;
+ const is = 1 - baseScale;
+
+ return {
+ delay,
+ duration,
+ css: (t) => {
+ const eased = easing(t);
+ return `opacity: ${eased * o}; transform: scale(${
+ eased * s * is + baseScale
+ })`;
+ },
+ };
+ }
+
+ let { messages, subject, user } = data;
+ let input, pane;
+
+ $: initialize($password);
+ let initialize = async (p) => {
+ await Promise.all(
+ messages.map(
+ async (event) => (event.message = await decrypt({ event, user }))
+ )
+ );
+
+ messages = messages;
+ tick().then(() => pane && (pane.scrollTop = pane.scrollHeight));
+ };
+
+ e.subscribe(async (event) => {
+ let found = ~messages.findIndex((m) => m.id === event?.id);
+ if (event?.recipient.id === user.id && !found) {
+ event.message = await decrypt({ event, user });
+ messages.push(event);
+ messages = messages;
+ tick().then(() => pane && (pane.scrollTop = pane.scrollHeight));
+ }
+ });
+
+ let sent, submitting, message;
+ let submit = async () => {
+ submitting = true;
+
+ let event = {
+ pubkey: user.pubkey,
+ created_at: Math.floor(Date.now() / 1000),
+ kind: 4,
+ tags: [["p", subject.pubkey]],
+ };
+
+ event.message = message;
+
+ message = "";
+
+ event.author = user;
+ event.recipient = subject;
+
+ messages.push(event);
+ messages = messages;
+ tick().then(() => pane && (pane.scrollTop = pane.scrollHeight));
+
+ try {
+ event.content = await encrypt({
+ message: event.message,
+ recipient: subject.pubkey,
+ user,
+ });
+ event.id = getEventHash(event);
+ await sign({ event, user });
+ await send(event);
+
+ sent = true;
+ } catch (e) {
+ console.log(e);
+ fail("Failed to send message");
+ }
+
+ message = "";
+ submitting = false;
+ };
+
+ let keydown = (e) => {
+ if (e.key === "Enter") {
+ e.preventDefault();
+ submit();
+ }
+ };
+</script>
+
+<div class="container max-w-xl mx-auto px-4 space-y-5">
+ <button
+ type="button"
+ class="hover:opacity-80"
+ data-sveltekit-preload-data="false"
+ on:click={back}
+ >
+ <Icon icon="arrow-left" style="w-10" />
+ </button>
+
+ <div
+ class="h-[50vh] max-h-[50vh] overflow-y-scroll scrollbar-thin scrollbar-thumb-[#F2F6FC] scrollbar-track-white pr-8"
+ bind:this={pane}
+ >
+ {#each messages as { id, author, message, created_at, pubkey }}
+ {@const ours = pubkey === user.pubkey}
+ {@const theirs = !ours}
+
+ {#if message}
+ <div
+ class="flex gap-2"
+ class:flex-row-reverse={theirs}
+ class:justify-end={theirs}
+ in:fadeScale={{
+ easing: cubicInOut,
+ baseScale: 0.5,
+ }}
+ >
+ <div
+ class="rounded-2xl px-4 py-2 max-w-[300px] mb-1 text-lg"
+ class:ours
+ class:theirs
+ :key={id}
+ >
+ {message}
+ </div>
+ <div class="mt-auto">
+ <Avatar user={author} size={"12"} />
+ </div>
+ </div>
+ <div class="text-sm text-gray-400 mb-6" class:text-right={ours}>
+ {format(new Date(created_at * 1000), "MMM d")},
+ {format(new Date(created_at * 1000), "h:mm aa")}
+ </div>
+ {/if}
+ {/each}
+ </div>
+
+ <form
+ method="POST"
+ class="w-full border rounded-xl outline-none gap-4 flex p-0 pr-2"
+ on:submit|preventDefault={submit}
+ >
+ <input type="hidden" name="requester_id" value={user.id} />
+ <input type="hidden" name="recipient" value={subject.username} />
+
+ <div
+ use:focus
+ contenteditable
+ class="grow break-all py-4 outline-none mt-0 pl-4"
+ bind:innerHTML={message}
+ on:keydown={keydown}
+ />
+ <button type="submit" class="my-auto shrink-0">
+ <Icon icon="send" style="w-8" />
+ </button>
+ </form>
+</div>
+
+<style>
+ .ours {
+ @apply bg-gradient-to-r from-[#F2F6FC] to-[#E1E3FF] text-black rounded-br-none ml-auto;
+ }
+
+ .theirs {
+ @apply bg-gray-100 text-black rounded-bl-none;
+ }
+</style>